(*
   Copyright (c) 2023-2025 Semgrep Inc.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public License
   version 2.1 as published by the Free Software Foundation.

   This library is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the file
   LICENSE for more details.
*)
(*****************************************************************************)
(* Console Experience *)
(*****************************************************************************)

let spinner = [| "⠋"; "⠙"; "⠹"; "⠸"; "⠼"; "⠴"; "⠦"; "⠧"; "⠇"; "⠏" |]

(* TODO: obey the global color settings instead: Console.get_highlight () *)
let should_show_spinner () = !ANSITerminal.isatty Unix.stdout && Sys.unix

let show_spinner delay_ms : unit =
  if not (should_show_spinner ()) then (
    ANSITerminal.printf [ ANSITerminal.green ] "Waiting for sign in...";
    (* Note: sleep is measured in seconds *)
    Unix.sleepf (Float.of_int delay_ms /. Float.of_int 1000))
  else
    let print_frame ~frame_index:i : unit =
      let spinner = spinner.(i mod Array.length spinner) in
      ANSITerminal.set_cursor 1 (-1);
      ANSITerminal.printf [ ANSITerminal.green ] "%s Waiting for sign in..."
        spinner
    in
    for frame_index = 1 to 100 do
      print_frame ~frame_index;
      (* Note: sleep is measured in seconds *)
      Unix.sleepf (Float.of_int delay_ms /. Float.of_int (1000 * 100))
    done

let erase_spinner () : unit =
  ANSITerminal.set_cursor 1 (-1);
  ANSITerminal.move_bol ();
  ANSITerminal.erase ANSITerminal.Below

let spinner_async () : 'a Lwt.t =
  (* nosemgrep *)
  ANSITerminal.(print_string [] "\027[?25l");
  (* hide cursor to make progess indicator more visible *)
  let jump_y = ref true in
  let print_frame ~frame_index:i : unit Lwt.t =
    let spinner = spinner.(i mod Array.length spinner) in
    ANSITerminal.move_bol ();
    (* ensure we update only the progress indicator *)
    if !jump_y then (
      (* jump to the line above to add the indicator *)
      ANSITerminal.move_cursor 0 (-1);
      jump_y := false)
    else ();
    ANSITerminal.printf [ ANSITerminal.green ] "%s" spinner;
    Lwt.return ()
  in
  (* create a cancellable promise *)
  let rec loop i =
    let%lwt _ = print_frame ~frame_index:i in
    let%lwt _ = Lwt_platform.sleep 0.05 in
    loop (i + 1)
  in
  Lwt.finalize
    (fun () -> loop 0)
    (fun () ->
      (* Flush any pending output to prevent surprises *)
      let%lwt () = Lwt_io.flush Lwt_io.stdout in
      (* nosemgrep *)
      ANSITerminal.(print_string [] "\027[?25h");
      erase_spinner ();
      Lwt.return_unit)
